#include <linux/init.h>
#include <linux/kallsyms.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/proc_fs.h>
#include <linux/slab.h>
#include <linux/stat.h>
#include <linux/uaccess.h>
// 数组模仿链表
// -------------------- manage -----------------------

struct app_ctl {
    char* dsc;
    char* mode;
};

#define LISTSIZE 10
// static const int LISTSIZE = 10;
struct app_ctl* list[LISTSIZE];
static int      list_node_num = 0;

void init_list(void)
{
    int i;
    for (i = 0; i < LISTSIZE; i++) {
        list[i] = NULL;
    }
    list_node_num = 0;
}

void update_list(struct app_ctl* node)
{
    list[list_node_num] = node;
    list_node_num++;
}

void print_list(void)
{
    int i;
    printk("--------------------------");
    for (i = 0; i < LISTSIZE; i++) {
        if (list[i]) {
            printk(KERN_EMERG "dsc:%s, mode:%s", list[i]->dsc, list[i]->mode);
        }
    }
}

void delete_list(void)
{
    int i;
    for (i = 0; i < LISTSIZE; i++) {
        if (list[i]) {
            kfree(list[i]->dsc);
            kfree(list[i]);
            list[i] = NULL;
        }
    }
}

// ---------------------------module dsc---------------------------

static char* dsc = NULL;
// module_param(dsc, charp, 0644);

// dsc setter callback
int my_param_set_string(const char* val, const struct kernel_param* kp)
{
    char*  buf;
    size_t len;
    int    res = param_set_charp(val, kp);  // Use helper for write variable
    if (res == 0) {
        if (dsc) {
            kfree(dsc);
            dsc = NULL;
        }

        len = strlen(val);
        buf = kmalloc(len, GFP_KERNEL);
        strncpy(buf, val, len);
        printk("set dsc %s", buf);
        dsc = buf;
    }
    return res;
}

// dsc getter callback
int my_param_get_string(char* buffer, const struct kernel_param* kp)
{
    int res = param_get_charp(buffer, kp);  // Use helper for write variable
    printk("read dsc");
    return res;
}

// dsc param ops
const struct kernel_param_ops my_param_ops_string = {
    .set = &my_param_set_string,  // Use our setter ...
    .get = &my_param_get_string,  // .. and standard getter
};

// filename
// operations
// pointer to variable, contained parameter's value
// permissions on file
module_param_cb(app_dsc, &my_param_ops_string, &dsc, 0644);

// -------------------------- module mode -----------------------------
static char*       mode_val = NULL;
static const char* mode_1   = "1";
static const char* mode_0   = "0";
// module_param(mode, charp, 0644);

// mode setter callback
int my_param_set_mode(const char* val, const struct kernel_param* kp)
{
    int res = param_set_charp(val, kp);  // Use helper for write variable
    struct app_ctl* node;
    if (res == 0 && dsc != NULL) {
        if (strncmp(mode_1, val, 1) == 0) {
            // printk("cmp 1");  // 相等
            mode_val = "1";
        } else if (strncmp(mode_0, val, 1) == 0) {
            // printk("cmp 0");  // 相等
            mode_val = "0";
        } else {
            printk("set invalid mode");
            return res;
        }
        printk("set mode %s", val);

        node = (struct app_ctl*)kmalloc(sizeof(struct app_ctl), GFP_KERNEL);
        node->dsc  = dsc;
        node->mode = mode_val;
        dsc        = NULL;
        update_list(node);
        print_list();
    }
    return res;
}

// mode getter callback
int my_param_get_mode(char* buffer, const struct kernel_param* kp)
{
    int res = param_get_charp(buffer, kp);
    // printk("string getter talks \n");
    return res;
}

// mode param ops
const struct kernel_param_ops my_param_ops_mode = {
    .set = &my_param_set_mode,  // Use our setter ...
    .get = &my_param_get_mode,  // .. and standard getter
};

module_param_cb(mode, &my_param_ops_mode, &mode_val, 0644);

// ------------module init and clean-----------

static int proc_init(void)
{
    printk("param-dsc init \n");
    init_list();
    return 0;
}

static void proc_cleanup(void)
{
    delete_list();
    printk("param-dsc uninstall \n");
}

module_init(proc_init);
module_exit(proc_cleanup);
MODULE_LICENSE("GPL");
